package core.course;

import generator.CourseBank;
import generator.CourseBankGenerator;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

import commonUtils.SerialClone;

import core.interfaces.IBooleanOperator;
import core.interfaces.IState;

public class TimeTable extends HashSet<Course> implements
IState<TimeTable>, TableModel, Serializable
{
	public static final int NUM_TIMESLOTS = 20;
	private static final long serialVersionUID = 108846309765913113L;
	private static final int MAX_DIFFICULTY = 10;
	
	/**
	 * a list of <b>all</b> the courses in the DB boolean operators
	 */
	private List<? extends IBooleanOperator> m_CoursesAsOperators = null;//new ArrayList<IBooleanOperator>();
	
	public static final String[] COLUMN_NAMES =
	{
			"Num",
			"Course"
	};
	
	
	public static TimeTable getEmptyInitialStateRandomCourses(int courseNumber, int seed)
	{
		CourseBank bank = new CourseBankGenerator().generateCourseBank(new Random(seed), courseNumber);
		TimeTable timeTable = new TimeTable(bank.getCourses());
		return timeTable;
	}
	
	@Override
	public TimeTable clone()
	{
		return SerialClone.clone(this);
	}
	
	@Override
	public String toString()
	{
		int size = this.size();
		if (size == 0)
		{
			return "EMPTY";
		}
		else
		{
			return size + " Courses";
		}
	}
	
	public TimeTable(List<Course> courses)
	{
		m_CoursesAsOperators = courses;//.addAll(courses);
	}
	
	public boolean isCoursesCollide()
	{
		BitSet bitSet = new BitSet(NUM_TIMESLOTS);
		for (Course course : this)
		{
			int time = course.getTime();
			if (bitSet.get(time))
			{
				return true;
			}
			bitSet.set(time);
		}
		return false;
	}
	
	public int collisionNumber()
	{
		int result = 0;
		BitSet bitSet = new BitSet(NUM_TIMESLOTS);
		for (Course course : this)
		{
			int time = course.getTime();
			if (bitSet.get(time))
			{
				++result;
			}
			bitSet.set(time);
		}
		return result;
		
	}
	
	public boolean shortcutOperatorExists()
	{
		return true;
	}
	
	public void doShortcutOperator()
	{
		this.reduceConflicts();
	}
	
	private void reduceConflicts()
	{
		Map<Integer, List<Course>> courses  = new HashMap<Integer, List<Course>>();
		BitSet bitSet = new BitSet(NUM_TIMESLOTS);
		
		//map courses by their time slot
		for(Course course: this)
		{
			int time = course.getTime();
			if(!bitSet.get(time))
			{
				//first course found in time slot
				List<Course> timeSlotList = new ArrayList<Course>();
				timeSlotList.add(course);
				courses.put(time, timeSlotList);
				
				bitSet.set(time);
				
			}
			else
			{
				//not the first course in this time slot - add to list
				courses.get(time).add(course);
			}
		}
		
		//select courses for time table
		this.reset();
		Random random = new Random(new Date().getTime());
		for(int i=0; i < NUM_TIMESLOTS ; ++i)
		{
			List<Course> collidingCourses = courses.get(i);
			if(collidingCourses != null)
			{
				//there is a possible course in this slot in the time table
				int selectedCourse = random.nextInt(collidingCourses.size());
				this.doOperator(collidingCourses.get(selectedCourse));				
			}
		}
	}
	
	
	/**
	 * 
	 * @return the number of free time slots in the timeTable
	 */
	public int freeTime()
	{
		BitSet bitSet = new BitSet(NUM_TIMESLOTS);
		int freeTime = 20;
		for(Course course: this)
		{
			int time = course.getTime();
			if(!bitSet.get(time))
			{
				//found another taken time slot.
				bitSet.set(time);
				--freeTime;
			}
		}
		return freeTime;
	}
	
	/**
	 * 
	 * @return difficulty partition  of the courses in the time table.
	 * 	for example: if there are 3 courses with difficulty 6 then result[6]==3
	 * there are MAX_DIFFICULTY+1 possible difficulties (0..MAX_DIFFICULTY)
	 */
	public int[] difficultyPartition()
	{
		int[] result = new int[MAX_DIFFICULTY+1]; //0..10
		for(Course course: this) 
		{
			++result[course.getWorkload()];
		}
		return result;
	}

	@Override
	public void doOperator(IBooleanOperator operator)
	{
		// assuming operator is Course to increase performances
		Course course = (Course)operator;
		
		if (this.contains(course))
		{
			this.remove(course);
		}
		else
		{
			this.add(course);
		}
		
	}

	@Override
	public List<? extends IBooleanOperator> getPossibleOperators()
	{
		return m_CoursesAsOperators;
	}

	@Override
	public TimeTable getState()
	{
		return this;
	}
	
	@Override
	public void reset()
	{
		this.clear();
	}
	
	public boolean isCourseExist(Course course)
	{
		boolean result = false;
		if(this.contains(course))
		{
			result = true;
		}
		
		return result;
	}

	@Override
	public void addTableModelListener(TableModelListener l)
	{
		
		
	}

	@Override
	public Class<?> getColumnClass(int columnIndex)
	{
		switch(columnIndex)
		{
		case 0: return Integer.class;
		case 1: return String.class;
		}
		return null;
	}

	@Override
	public int getColumnCount()
	{
		return COLUMN_NAMES.length;
	}

	@Override
	public String getColumnName(int columnIndex)
	{
		return COLUMN_NAMES[columnIndex];
	}

	@Override
	public int getRowCount()
	{
		return this.size();
	}

	@Override
	public Object getValueAt(int rowIndex, int columnIndex)
	{

		switch(columnIndex)
		{
		case 0: return rowIndex + 1;
		case 1: 
			Object[] arr = this.toArray();
			return arr[rowIndex];
		}
		return null;
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex)
	{
		return false;
	}

	@Override
	public void removeTableModelListener(TableModelListener l)
	{
		
	}

	@Override
	public void setValueAt(Object value, int rowIndex, int columnIndex)
	{
		
	}

	public int getMaxDifficulty()
	{
		return MAX_DIFFICULTY;
	}
}
